package com.sh.data.engine.domain.workspace.index.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.sh.data.engine.common.constants.Constants;
import com.sh.data.engine.common.enumDefinition.DSType;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.common.util.ServletUtils;
import com.sh.data.engine.domain.authority.service.RoleService;
import com.sh.data.engine.domain.authority.service.UserProjectService;
import com.sh.data.engine.domain.base.model.PageResult;
import com.sh.data.engine.domain.base.model.UserContext;
import com.sh.data.engine.domain.base.model.enums.Database;
import com.sh.data.engine.domain.integration.datasource.model.param.DataSourceSaveParam;
import com.sh.data.engine.domain.integration.datasource.service.DataSourceService;
import com.sh.data.engine.domain.normalization.research.themedesign.service.ThemeService;
import com.sh.data.engine.domain.shims.DbManagerFactory;
import com.sh.data.engine.domain.shims.db.BaseDbManager;
import com.sh.data.engine.domain.shims.db.DbOptions;
import com.sh.data.engine.domain.util.ConvertUtil;
import com.sh.data.engine.domain.workspace.index.model.domain.ProjectDomain;
import com.sh.data.engine.domain.workspace.index.model.domain.ProjectTabviewDomain;
import com.sh.data.engine.domain.workspace.index.model.mapper.ProjectDomainMapper;
import com.sh.data.engine.domain.workspace.index.model.mapper.StorageDetailDomainMapper;
import com.sh.data.engine.domain.workspace.index.service.ProjectService;
import com.sh.data.engine.domain.workspace.manager.model.domain.StorageDetailDomain;
import com.sh.data.engine.domain.workspace.manager.model.domain.UserDomain;
import com.sh.data.engine.domain.workspace.manager.service.UserService;
import com.sh.data.engine.domain.workspace.manager.service.impl.StorageServiceImpl;
import com.sh.data.engine.repository.dao.authority.entity.RoleEntity;
import com.sh.data.engine.repository.dao.authority.entity.UserProjectEntity;
import com.sh.data.engine.repository.dao.workspace.index.ProjectMapper;
import com.sh.data.engine.repository.dao.workspace.index.entity.ProjectEntity;
import com.sh.data.engine.repository.dao.workspace.manager.entity.StorageEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: mengzheng.mhc
 * @date: 2024/6/19 22:36
 */
@Service
@Slf4j
public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, ProjectEntity>
    implements ProjectService {

    @Lazy
    @Autowired
    private ThemeService themeService;

    @Lazy
    @Autowired
    private StorageServiceImpl storageServiceImpl;

    @Lazy
    @Autowired
    private DataSourceService dataSourceService;

    @Lazy
    @Autowired
    private UserService userService;

    @Lazy
    @Autowired
    private UserProjectService userProjectService;

    @Lazy
    @Autowired
    private RoleService roleService;

    @Autowired
    private ProjectDomainMapper projectDomainMapper;

    private static final ProjectEntity DEMO_PROJECT = ProjectEntity.builder()
        .name("数据集成引擎默认空间")
        .rowState(1)
        .storageId(1L)
        .isUseStorageId(1)
        .build();

    @Autowired
    private StorageDetailDomainMapper storageDetailDomainMapper;

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = "engineTransactionManager")
    public Boolean saveProject(Long id, String name, Long storageId) {
        name = StringUtils.trimToEmpty(name);
        final String userId = UserContext.getUserId();

        if (id == null) {
            long count =
                this.count(
                    new LambdaQueryWrapper<ProjectEntity>()
                        .eq(ProjectEntity::getName, name)
                        .eq(ProjectEntity::getRowState, 1));
            if (count > 0) {
                throw new BusinessException("项目名称已被租户内其他项目占用，请修改名称后重新尝试");
            }
            ProjectEntity project = new ProjectEntity();
            project.setName(name);

            List<ProjectEntity> strId =
                this.list(
                    new LambdaQueryWrapper<ProjectEntity>()
                        .eq(ProjectEntity::getRowState, 1)
                        .eq(ProjectEntity::getStorageId, storageId));
            if (CollectionUtils.isEmpty(strId)) {
                project.setStorageId(storageId);
            } else {
                throw new BusinessException("存储资源已被项目占用，请选择其他存储资源或者新建一个存储资源");
            }

            project.setRowState(1);
            project.setCreatorId(userId);
            project.setUpdaterId(userId);
            project.setCreateTime(new Date());
            this.save(project);
            initProject(project.getId(), userId);
            StorageDetailDomain detailStorage = storageServiceImpl.getDetailStorage(storageId, false);
            DataSourceSaveParam dataSourceParam = detailStorage.convertToDataSourceSaveParam();
            dataSourceParam.setProjectId(project.getId());
            ServletUtils.setRequestAttribute(Constants.PARAM_PROJECT_ID, project.getId());
            dataSourceService.saveDataSource(dataSourceParam);
        } else {
            long count =
                this.count(
                    new LambdaQueryWrapper<ProjectEntity>()
                        .ne(ProjectEntity::getId, id)
                        .eq(ProjectEntity::getName, name)
                        .eq(ProjectEntity::getRowState, 1));
            if (count > 0) {
                throw new BusinessException("项目名称已被租户内其他项目占用，请修改名称后重新尝试");
            }
            ProjectEntity entity = new ProjectEntity();
            entity.setId(id);
            entity.setName(name);
            entity.setStorageId(storageId);
            entity.setRowState(1);
            entity.setUpdateTime(new Date());
            entity.setUpdaterId(userId);
            this.updateById(entity);
//            LogContext.put(LogKey.ISUPDATE.getKey(), LogKey.ISUPDATE.getValue());
        }
        return true;
    }

    @Override
    public PageResult<ProjectDomain> listProjectAuth(Integer pageNumber, Integer pageSize, String name) {
        final String userId = UserContext.getUserId();

        final UserDomain userDomain = userService.getUserById(userId);

        LambdaQueryWrapper<ProjectEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ProjectEntity::getRowState, 1);
        lambdaQueryWrapper.orderByDesc(ProjectEntity::getCreateTime);
        // 如果不是系统管理员，则根据userauth来判断他有哪些项目
        if (userDomain != null) {
            RoleEntity byId = roleService.getById(userDomain.getRoleId());
            if (!Objects.equals(byId.getIsSystem(), 1)) {

                List<Long> projectIds =
                    userProjectService
                        .list(
                            new LambdaQueryWrapper<UserProjectEntity>()
                                .eq(UserProjectEntity::getUserId, userId))
                        .stream()
                        .map(UserProjectEntity::getProjectId)
                        .collect(Collectors.toList());

                if (CollectionUtils.isEmpty(projectIds)) {
                    return new PageResult<>();
                }

                lambdaQueryWrapper.in(ProjectEntity::getId, projectIds);
            }
        }

        if (StringUtils.isNotBlank(name)) {
            lambdaQueryWrapper.like(ProjectEntity::getName, name);
        }

        Page<ProjectEntity> projectEntityPageResult =
            this.page(new Page<>(pageNumber, pageSize), lambdaQueryWrapper);

        final List<ProjectEntity> entityList = projectEntityPageResult.getRecords();

        final List<String> userIds =
            entityList.stream().map(e -> e.getUpdaterId()).collect(Collectors.toList());

        final Set<Long> storageIds =
            entityList.stream().map(e -> e.getStorageId()).collect(Collectors.toSet());
        final Map<String, String> userMap = userService.getMapByUserIds(userIds);

        List<StorageEntity> storageEntityList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(storageIds)) {
            storageEntityList = storageServiceImpl.listByIds(storageIds);
        }

        final Map<Long, StorageEntity> storageEntityMap =
            storageEntityList.stream().collect(Collectors.toMap(x -> x.getId(), x -> x));

        List<ProjectDomain> projectDomains = new ArrayList<>();

        entityList.forEach(
            e -> {
                ProjectDomain projectDomain = new ProjectDomain();
                projectDomain.setId(e.getId());
                projectDomain.setName(e.getName());
                projectDomain.setStorageId(e.getStorageId());
//                projectDomain.setStorageName(
//                    storageEntityMap.get(e.getStorageId()) == null
//                        ? null
//                        : storageEntityMap.get(e.getStorageId()).getStorageName());
                projectDomain.setUpdateTime(e.getUpdateTime());
                if (e.getUpdaterId() != null && userMap.containsKey(e.getUpdaterId())) {
                    projectDomain.setUpdaterId(userMap.get(e.getUpdaterId()));
                }
                projectDomains.add(projectDomain);
            });

        final PageResult<ProjectDomain> pageInfo =
            PageResult.copyPropertiesAndConvert(projectEntityPageResult, ProjectDomain.class);
        pageInfo.setResult(projectDomains);

        return pageInfo;
    }

    @Override
    public List<ProjectEntity> listProjectAuth() {
        final String userId = UserContext.getUserId();
        LambdaQueryWrapper<ProjectEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ProjectEntity::getRowState, 1);

        final UserDomain userDomain = userService.getUserById(userId);
        if (userDomain == null) {
            // TODO: 引入用户体系后移除
            return this.list(lambdaQueryWrapper);
        }
        if (userDomain.getRoleId() == 0) {
            // 如果用户尚未分配角色，或者是3员（3员没有角色）
            return this.list(lambdaQueryWrapper);
        }

        RoleEntity byId = roleService.getById(userDomain.getRoleId());

        // 如果不是系统管理员，则根据userauth来判断他有哪些项目
        if (!Objects.equals(byId.getIsSystem(), 1)) {

            List<Long> projectIds =
                userProjectService
                    .list(
                        new LambdaQueryWrapper<UserProjectEntity>()
                            .eq(UserProjectEntity::getUserId, userId))
                    .stream()
                    .map(UserProjectEntity::getProjectId)
                    .collect(Collectors.toList());

            lambdaQueryWrapper.in(ProjectEntity::getId, projectIds);
        }

        final List<ProjectEntity> entityList = this.list(lambdaQueryWrapper);
        return entityList;
    }

    @Override
    public boolean deleteProject(Long projectId) {
        ProjectEntity projectEntity = this.getById(projectId);
        if (projectEntity == null) {
            throw new BusinessException("查询不到projectId为:" + projectId + "的空间，无法删除");
        }

        List<UserProjectEntity> userProjectEntities =
            userProjectService.list(
                new LambdaQueryWrapper<UserProjectEntity>()
                    .eq(UserProjectEntity::getProjectId, projectId));

        if (CollectionUtils.isNotEmpty(userProjectEntities)) {
            throw new BusinessException("该空间被用户使用中，无法删除。请至《空间权限》解绑。");
        }

        projectEntity.setRowState(0);
        this.updateById(projectEntity);
        return true;
    }

    @Override
    public ProjectEntity getProjectById(Long projectId) {
        return this.getById(projectId);
    }

    @Override
    public List<ProjectDomain> getAllProject() {

        List<ProjectEntity> multi =
            this.list(new LambdaQueryWrapper<ProjectEntity>().eq(ProjectEntity::getRowState, 1));

        final Set<Long> storageIds =
            multi.stream().map(p -> p.getStorageId()).collect(Collectors.toSet());

        if (CollectionUtils.isEmpty(storageIds)) {
            return Lists.newArrayList();
        }

        final List<StorageEntity> storageEntityList = storageServiceImpl.listByIds(storageIds);

        final Map<Long, StorageEntity> storageEntityMap =
            storageEntityList.stream().collect(Collectors.toMap(x -> x.getId(), x -> x));

        List<ProjectDomain> projectDomains = ConvertUtil.copyProperties(multi, ProjectDomain.class);

        for (int i = 0; i < multi.size(); i++) {
            StorageEntity storage = storageEntityMap.get(multi.get(i).getStorageId());
            StorageDetailDomain storageDetailDomain = storageDetailDomainMapper.map(storage);
            if (Objects.nonNull(storage)) {
                storageDetailDomain.setId(storage.getId());
                if (Objects.isNull(storage.getRmdbs())) {
                    continue;
                }
                storageDetailDomain.setDbName(storage.getRmdbs().getDbName());
                storageDetailDomain.setUsername(storage.getRmdbs().getUsername());
                storageDetailDomain.setPassword(storage.getRmdbs().getPassword());
                storageDetailDomain.setSchema(storage.getRmdbs().getSchema());
            }
            projectDomains.get(i).setStorageDetailDomain(storageDetailDomain);
        }

        return projectDomains;
    }

    @Override
    public List<ProjectEntity> getByStorageId(Long storageId) {
        List<ProjectEntity> list =
            list(new LambdaQueryWrapper<ProjectEntity>().eq(ProjectEntity::getStorageId, storageId));
        return list;
    }

    @Override
    public BaseDbManager getDbManager(Long projectId) {
        return getDbManager(projectId, null);
    }

    @Override
    public BaseDbManager getDbManager(Long projectId, Integer connLoginTimeout) {
        final StorageEntity storageEntity = storageServiceImpl.tmpGetStorageInfoByProjectId(projectId);

        if (null == storageEntity) {
            return null;
        }

        final String storageType = storageEntity.getStorageType();
        final DSType dsType = DSType.from(storageType);

        if (null == dsType) {
            throw new BusinessException("不支持的数据源类型");
        }

        DbOptions options = new DbOptions();
        options.setUrl(storageEntity.getStorageLink());
        options.setUsername(storageEntity.getRmdbs().getUsername());
        options.setPassword(storageEntity.getRmdbs().getPassword());
        options.setLoginTimeout(connLoginTimeout);

        Database database;

        switch (dsType) {
            case Hive:
                database = Database.HIVE;
                options.setHiveSiteXmlPath(storageEntity.getRmdbs().getHiveSiteXmlPath());
                options.setDatabase(database);
                break;
            case PostgreSQL:
            case MatrixDB:
                database = Database.POSTGRESQL;
                options.setDatabase(database);
                options.setSchema(storageEntity.getRmdbs().getSchema());
                break;
            case TiDB:
                database = Database.TIDB;
                options.setDatabase(database);
                break;
            case Sap:
                database = Database.SAP;
                options.setDatabase(database);
                break;
            case KINGBASE8:
                database = Database.KINGBASE8;
                options.setDatabase(database);
                options.setSchema(storageEntity.getRmdbs().getSchema());
                break;
            case DM:
                database = Database.DM;
                options.setDatabase(database);
                break;
            default:
                throw new BusinessException("不支持的数据源类型");
        }

        final BaseDbManager dbManager = DbManagerFactory.getDbManager(database, options);

        return dbManager;
    }

    @Override
    public List<ProjectEntity> findAll() {
        List<ProjectEntity> list =
            list(new LambdaQueryWrapper<ProjectEntity>().eq(ProjectEntity::getRowState, 1));
        return list;
    }

    @Override
    public Map<Long, String> getProjectMapByIds(List<Long> ids) {
        List<ProjectEntity> list =
            list(new LambdaQueryWrapper<ProjectEntity>().in(ProjectEntity::getId, ids));

        return list.stream()
            .collect(Collectors.toMap(ProjectEntity::getId, ProjectEntity::getName, (k1, k2) -> k1));
    }

    @Override
    public IPage<ProjectTabviewDomain> tabview(Integer pageNum, Integer pageSize) {
        // TODO:
        return null;
    }

    //    private void initProject(Long projectId, Long userId) {
    private void initProject(Long projectId, String userId) {
        // 为该工作空间复制一份初始主题并插入
        themeService.insertInitialThemes(projectId, userId);
        // TODO: 删除了之前需要为每个project在minio中创建用户和保存ak、sk的逻辑
    }
}
